// <copyright file="ShipComponent.cs" company="SpaceRock Team Members">
// Project: SpaceRock
// File: ShipComponent.cs (18,29 KB, 515 lines)
// Creation date: 2010-05-28 13:53
// Last modified: 2010-06-13 03:05
// </copyright>

#region Using directives
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Oops.Xna.Framework;
using Oops.Xna.Framework.Physics;
using Particle3DSample;
using SpaceRockTeam.SpaceRock.Data;
using System;
using System.Collections.Generic;
#endregion


namespace SpaceRockTeam.SpaceRock.Components
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class ShipComponent : Microsoft.Xna.Framework.DrawableGameComponent, IExplosionListener
    {
        /// <summary>
        /// Collision shape
        /// </summary>
        public CollisionBox collisionShape;

        /// <summary>
        /// Missle move factor
        /// </summary>
        private const float MissleMoveFactor = 100;

        /// <summary>
        /// Move factor
        /// </summary>
        private const float MoveFactor = 10000.0f;

        /// <summary>
        /// Rotate factor
        /// </summary>
        private const float RotateFactor = 0.003f;

        /// <summary>
        /// _Space key pressed
        /// </summary>
        private bool spaceKeyPressed = false;

        /// <summary>
        /// Explosion particles
        /// </summary>
        private ParticleSystem explosionParticles;

        /// <summary>
        /// Explosion smoke particles
        /// </summary>
        private ParticleSystem explosionSmokeParticles;

        /// <summary>
        /// Projectile trail particles
        /// </summary>
        private ParticleSystem projectileTrailParticles;

        /// <summary>
        /// Projectiles
        /// </summary>
        private List<IProjectile> projectiles = new List<IProjectile>();

        /// <summary>
        /// _Explode sound
        /// </summary>
        private SoundEffect missleLaunchSound;

        /// <summary>
        /// _Explosion sound
        /// </summary>
        private SoundEffect explosionSound;

        /// <summary>
        /// _Ship explosion sound
        /// </summary>
        private SoundEffect shipExplosionSound;

        /// <summary>
        /// _Ship engine sound
        /// </summary>
        private SoundEffect shipEngineSound;

        /// <summary>
        /// Smoke plume particles
        /// </summary>
        private ParticleSystem smokePlumeParticles;

        /// <summary>
        /// Fire particles
        /// </summary>
        private ParticleSystem[] fireParticles;

        /// <summary>
        /// Position
        /// </summary>
        public Vector3 Position { get; set; } // Position

        /// <summary>
        /// Rotation
        /// </summary>
        public float Rotation { get; set; } // Rotation

        /// <summary>
        /// Model name
        /// </summary>
        public virtual string ModelName
        {
            get { return "BasicShip"; }
        } // ModelName

        /// <summary>
        /// Model ratio
        /// </summary>
        public virtual float ShipScale
        {
            get { return 0.017f; }
        } // ShipScale
        
        /// <summary>
        /// Model
        /// </summary>
        private Model model { get; set; } // Model

        /// <summary>
        /// Lives count
        /// </summary>
        public uint LivesCount { get; set; } // LivesCount

        /// <summary>
        /// Destroyed
        /// </summary>
        public bool Destroyed { get; set; } // Destroyed

        /// <summary>
        /// _last engine sound time
        /// </summary>
        private TimeSpan lastEngineSoundTime;

        /// <summary>
        /// Prev position
        /// </summary>
        private Vector3 prevPosition = Vector3.Zero;

        /// <summary>
        /// Random
        /// </summary>
        private Random random;

        /// <summary>
        /// World
        /// </summary>
        public Matrix World
        {
            get
            {
                Vector3 rotation = collisionShape.CollisionGroup.RigidBody.Rotation;
                return Matrix.CreateScale(ShipScale) *
                        Matrix.CreateRotationX(MathHelper.ToRadians(-90)) * /* model rotation fix */
                        Matrix.CreateRotationX(rotation.X) *
                        Matrix.CreateRotationY(rotation.Y) *
                        Matrix.CreateRotationZ(rotation.Z) *
                        Matrix.CreateTranslation(collisionShape.Position);
            } // get
        } // World

        /// <summary>
        /// Physics
        /// </summary>
        public PhysicsComponent Physics { get; set; } // Physics

        /// <summary>
        /// Create ship component
        /// </summary>
        public ShipComponent(Game game)
            : base(game)
        {
            LivesCount = GameConfiguration.GetConfiguration().LivesCount;

            smokePlumeParticles = new SmokePlumeParticleSystem(Game, Game.Content);
            fireParticles = new FireParticleSystem[2]
                {
                    new FireParticleSystem(Game, Game.Content),
                    new FireParticleSystem(Game, Game.Content)
                };

            smokePlumeParticles.DrawOrder = 100;
            fireParticles[0].DrawOrder = 500;
            fireParticles[1].DrawOrder = 500;

            explosionParticles = new ExplosionParticleSystem(game, game.Content);
            explosionSmokeParticles = new ExplosionSmokeParticleSystem(game, game.Content);
            projectileTrailParticles = new ProjectileTrailParticleSystem(game, game.Content);

            explosionSmokeParticles.DrawOrder = 500;
            projectileTrailParticles.DrawOrder = 500;
            explosionParticles.DrawOrder = 500;
        } // ShipComponent

        /// <summary>
        /// Unload content
        /// </summary>
        protected override void UnloadContent()
        {
            Physics.RigidBodies.Remove(collisionShape.CollisionGroup.RigidBody);
            base.UnloadContent();
        } // UnloadContent

        /// <summary>
        /// Update bounding box
        /// </summary>
        private void UpdateBoundingBox(BoundingBox box, Vector3 position)
        {
            // Get the Center of the Box 
            Vector3 center = box.Max - box.Min;

            // Get the Offset from the position (center of model) 
            Vector3 offset = position - center;

            // Adjust Box to match model 
            box.Max += offset;
            box.Min += offset;
        } // UpdateBoundingBox

        /// <summary>
        /// Load content
        /// </summary>
        protected override void LoadContent()
        {
            model = Game.Content.Load<Model>("Models/" + ModelName);
            missleLaunchSound = Game.Content.Load<SoundEffect>("Sounds/MissleLaunch");
            explosionSound = Game.Content.Load<SoundEffect>("Sounds/ExplodeLarge");
            shipExplosionSound = Game.Content.Load<SoundEffect>("Sounds/ShipExplosion");
            shipEngineSound = Game.Content.Load<SoundEffect>("Sounds/ShipEngine");
            
            smokePlumeParticles.Initialize();
            fireParticles[0].Initialize();
            fireParticles[1].Initialize();
            explosionSmokeParticles.Initialize();
            projectileTrailParticles.Initialize();
            explosionParticles.Initialize();

            collisionShape = new CollisionBox();
            collisionShape.Tag = this;
            BoundingBox boundingBox = new BoundingBox();
            foreach (ModelMesh mesh in model.Meshes)
            {
                VertexPositionNormalTexture[] vertices =
                  new VertexPositionNormalTexture[mesh.VertexBuffer.SizeInBytes / VertexPositionNormalTexture.SizeInBytes];
                mesh.VertexBuffer.GetData<VertexPositionNormalTexture>(vertices);
                Vector3[] vertexs = new Vector3[vertices.Length];
                for (int index = 0; index < vertexs.Length; index++)
                    vertexs[index] = vertices[index].Position;
                boundingBox = BoundingBox.CreateMerged(boundingBox,
                  BoundingBox.CreateFromPoints(vertexs));
            } // foreach
            UpdateBoundingBox(boundingBox, Position);
            random = new Random();
            collisionShape.Extents = boundingBox.GetExtents() * ShipScale;
            RigidBody shipBody = new RigidBody();
            shipBody.VelocityLimit = 100;
            shipBody.Acceleration = Vector3.Zero;
            shipBody.CollisionGroup.CollisionShapes.Add(collisionShape);
            shipBody.CalculateMassProperties(10.0f);

            Vector3 position = Position;
            Matrix rotation = Matrix.CreateFromYawPitchRoll(0, 0, Rotation);
            shipBody.UpdateTransform(ref position, ref rotation);

            Physics.RigidBodies.Add(shipBody);
            CalculatePreviousPosition();
            base.LoadContent();
        } // LoadContent

        /// <summary>
        /// Update ship position using constraints
        /// </summary>
        private void UpdateShipPositionUsingConstraints()
        {
            Viewport viewport = GraphicsDevice.Viewport;
            SimpleCamera camera = (SimpleCamera)Game.Services.GetService(typeof(SimpleCamera));

            Matrix cameraWorld = Matrix.Identity;
            Vector3 viewportPosition = viewport.Project(collisionShape.Position,
                camera.Projection, camera.View, cameraWorld);

            int margin = 5;
            int fix = 5;
            Vector2 viewportPoint = new Vector2(viewportPosition.X, viewportPosition.Y);
            Vector2 newPoint = viewportPoint;

            if (viewportPosition.X < margin)
                newPoint.X = viewport.Width - margin - fix;

            if (viewportPosition.X > viewport.Width - margin)
                newPoint.X = margin + fix;

            if (viewportPosition.Y < margin)
                newPoint.Y = viewport.Height - margin - fix;

            if (viewportPosition.Y > viewport.Height - margin)
                newPoint.Y = margin + fix;

            if (newPoint != viewportPoint)
            {
                Vector3 output = viewport.Unproject(new Vector3(newPoint.X, newPoint.Y, viewportPosition.Z),
                    camera.Projection, camera.View, cameraWorld);
                output.Z = 0;
                collisionShape.Position = output;
                collisionShape.CollisionGroup.RigidBody.Position = output;
            }
        } // UpdateShipPositionUsingConstraints

        /// <summary>
        /// Calculate previous position
        /// </summary>
        public void CalculatePreviousPosition()
        {
            prevPosition = collisionShape.CollisionGroup.RigidBody.Position;
        } // CalculatePreviousPosition

        /// <summary>
        /// Update
        /// </summary>
        public override void Update(GameTime gameTime)
        {
            UpdateProjectiles(gameTime);

            if (!Destroyed)
            {
                Vector3 direction = Vector3.Transform(Vector3.Down,
                    Matrix.CreateRotationZ(collisionShape.CollisionGroup.RigidBody.Rotation.Z));
                Vector3 tailPosition = collisionShape.CollisionGroup.RigidBody.Position + direction * 10;
                fireParticles[0].AddParticle(tailPosition + new Vector3(0.0f, 0.0f, 0.0f), direction * 40);
                fireParticles[1].AddParticle(tailPosition, direction * 40);

                KeyboardState keyboardState = Keyboard.GetState();
                if (keyboardState.IsKeyDown(Keys.Up))
                {
                    fireParticles[0].AddParticle(tailPosition, direction * 40 * 2);
                    fireParticles[1].AddParticle(tailPosition, direction * 40 * 2);
                }
            } // if

            smokePlumeParticles.Update(gameTime);
            fireParticles[0].Update(gameTime);
            fireParticles[1].Update(gameTime);
            
            explosionParticles.Update(gameTime);
            explosionSmokeParticles.Update(gameTime);
            projectileTrailParticles.Update(gameTime);

            UpdateShipPositionUsingConstraints();
            base.Update(gameTime);
        } // Update

        /// <summary>
        /// Draw
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            SimpleCamera camera = (SimpleCamera)Game.Services.GetService(typeof(SimpleCamera));

            smokePlumeParticles.SetCamera(camera.View, camera.Projection);
            fireParticles[0].SetCamera(camera.View, camera.Projection);
            fireParticles[1].SetCamera(camera.View, camera.Projection);
            explosionParticles.SetCamera(camera.View, camera.Projection);
            explosionSmokeParticles.SetCamera(camera.View, camera.Projection);
            projectileTrailParticles.SetCamera(camera.View, camera.Projection);

            if (!Destroyed)
            {
                Matrix[] transformations = new Matrix[model.Bones.Count];
                model.CopyAbsoluteBoneTransformsTo(transformations);

                foreach (ModelMesh mesh in model.Meshes)
                {
                    foreach (BasicEffect effect in mesh.Effects)
                    {
                        effect.EnableDefaultLighting();
                        effect.TextureEnabled = true;
                        effect.Projection = camera.Projection;
                        effect.View = camera.View;
                        effect.World = /*transformations[mesh.ParentBone.Index] * */ World;
                    }
                    mesh.Draw();
                } // foreach
            } // if

            smokePlumeParticles.Draw(gameTime);
            fireParticles[0].Draw(gameTime);
            fireParticles[1].Draw(gameTime);
            explosionParticles.Draw(gameTime);
            explosionSmokeParticles.Draw(gameTime);
            projectileTrailParticles.Draw(gameTime);

            base.Draw(gameTime);
        } // Draw

        /// <summary>
        /// Helper for updating the list of active projectiles.
        /// </summary>
        void UpdateProjectiles(GameTime gameTime)
        {
            int i = 0;

            while (i < projectiles.Count)
            {
                if (!projectiles[i].Update(gameTime))
                {
                    // Remove projectiles at the end of their life.
                    projectiles.RemoveAt(i);
                }
                else
                    i++;
            } // while
        } // UpdateProjectiles

        /// <summary>
        /// Handle input
        /// </summary>
        public void HandleInput(GameTime gameTime, KeyboardState keyboardState, GamePadState gamePadState)
        {
            if (keyboardState.IsKeyDown(Keys.Left))
            {
                collisionShape.CollisionGroup.RigidBody.Rotation.Z
                    += gameTime.ElapsedGameTime.Milliseconds * RotateFactor;
            }

            if (keyboardState.IsKeyDown(Keys.Right))
            {
                collisionShape.CollisionGroup.RigidBody.Rotation.Z
                    -= gameTime.ElapsedGameTime.Milliseconds * RotateFactor;
            }

            if (keyboardState.IsKeyDown(Keys.Up))
            {
                Vector3 direction = Vector3.Transform(Vector3.Up,
                    Matrix.CreateRotationZ(collisionShape.CollisionGroup.RigidBody.Rotation.Z));
                direction *= gameTime.ElapsedGameTime.Milliseconds * MoveFactor;
                collisionShape.CollisionGroup.RigidBody.ApplyForceImpulse(direction);

                if ((gameTime.TotalGameTime - lastEngineSoundTime).Milliseconds > 500)
                {
                    lastEngineSoundTime = gameTime.TotalGameTime;
                    shipEngineSound.Play();
                }
            } // if

            if (keyboardState.IsKeyDown(Keys.Space))
                spaceKeyPressed = true;
            if (spaceKeyPressed && keyboardState.IsKeyUp(Keys.Space))
            {
                Vector3 missleDirection = Vector3.Transform(Vector3.Up,
                    Matrix.CreateRotationZ(collisionShape.CollisionGroup.RigidBody.Rotation.Z));

                Vector3 misslePosition = collisionShape.CollisionGroup.RigidBody.Position + missleDirection * 20;
                Projectile projectile = new Projectile(explosionParticles,
                                                               explosionSmokeParticles,
                                                               projectileTrailParticles,
                                                               misslePosition,
                                                               missleDirection * MissleMoveFactor,
                                                               this,
                                                               Physics);
                projectiles.Add(projectile);
                spaceKeyPressed = false;
                missleLaunchSound.Play();

            } // if

        } // HandleInput


        #region IExplosionListener Members

        /// <summary>
        /// On explosion
        /// </summary>
        public void OnExplosion(IProjectile projectile)
        {
            if (projectile is ShipExplosionProjectile)
                shipExplosionSound.Play();
            else
                explosionSound.Play();

        } // OnExplosion

        #endregion
        
        /// <summary>
        /// Explode
        /// </summary>
        public void Explode()
        {
            if (Destroyed) return;
            LivesCount = LivesCount - 1;

            Random random = new Random();
            for (int i = 0; i < 3; i++)
            {
                Vector3 randomOffset = new Vector3((float)random.NextDouble() * 10.0f - 5.0f,
                   (float)random.NextDouble() * 10.0f - 5.0f,
                   0.0f);
                ShipExplosionProjectile projectile = new ShipExplosionProjectile(explosionParticles,
                   explosionSmokeParticles,
                   collisionShape.CollisionGroup.RigidBody.Position + randomOffset,
                   Vector3.Zero,
                   this);
                projectile.ProjectileLifeSpan = (float)(0.5f + random.NextDouble());
                if (i == 0) projectile.ShouldExplode = true;

                projectiles.Add(projectile);
            } // for
            Destroyed = true;
        } // Explode

        /// <summary>
        /// Reset
        /// </summary>
        public void Reset()
        {
            Destroyed = false;
            collisionShape.CollisionGroup.RigidBody.Position = Position;
            collisionShape.CollisionGroup.RigidBody.Force = Vector3.Zero;
            collisionShape.CollisionGroup.RigidBody.Acceleration = Vector3.Zero;
            collisionShape.CollisionGroup.RigidBody.Velocity = Vector3.Zero;
        } // Reset
    } // class ShipComponent
} // namespace SpaceRockTeam.SpaceRock.Components
